<h2>背景</h2>
<p>之前分享过一个拖拽recycleview的item到列表之外的功能，分析了实现的难点，现在继续分享实现过程。</p>
<p>之前的文章清空这里https: //www.sunofbeach.net/a/842817116162752512</p>
<h3>分析功能</h3>
<ul>
    <li>之前提到过，rv的item只能显示在rv所在的区域中，item的view是不能显示在外面的，这一点已经卡主我们了</li>
    <li>既然不能，那我们复制一份itemView，然后手指移动的时候把这个复制品跟随手指移动，可行？</li>
    <li>复制后的view放哪里？我们在根布局可以新增透明的容器来容纳它</li>
    <li>复制触发之后，把复制的view显示在原来item的位置上，然后把item的内容隐藏，手指移动的时候item就消失了</li>
    <li>当手指松开的时候，做个动画，item跑回来开始的位置上，隐藏同时把item原来的数据显示出来</li>
    <li>复制品移动到某个区域中松开的时候，就触发删除，当然可以是任意逻辑，删除了就删除rv中对用的position，更新adapter</li>
</ul>
<hr />
<p>基本上就是这么做了，当时我把上面的逻辑写在本子上，一个一个实现，最后真的做好了。思路很重要，剩下就是api和编译了，好的电脑可以让你省很多时间，调试编译最耗时。</p>
<h3>复制一个View</h3>
<p>何时复制？我们的条件是：长按复制。 给rv的item整一个长按事件，假设你已经给rv设置长按事件了，怎么实现，方法很多，这里就不展开，代码是kt的如果是java的同学看不懂那就将就将就.</p>
<pre><code class="language-kotlin">    override fun onItemLongClick(adapter: BaseQuickAdapter&lt;*, *&gt;, view: View, position: Int): Boolean {
        //长按事件
        generateItem(adapter, view, position)
        //消费这个长按
        return true
    }
</code></pre>
<p>长按事件触发，这个容易理解，先尝试复制</p>
<pre><code class="language-kotlin">    private fun generateNewItem(view: View?) {
        view?.let {
            //得到当前长按view的在屏幕中位置
            it.getLocationOnScreen(clickPosition)
            //得到这个view
            val bitmap = getBitmapByItemView(view)
            //生成一个新的imageview，这个是因为我这里item是个图片，这个可以是任意的view
            dragItem = ImageView(this)
            //通过bitmap复制一个出来
            val newBitmap = copyItemView(bitmap)
            //设置这个新的view的宽高，和之前那个一样
            val lp = FrameLayout.LayoutParams(newBitmap.width, newBitmap.height)
            //把长按的时候记录的相对屏幕的坐标xy设置给新的复制品，让他们重叠在一起
            lp.leftMargin = clickPosition.get(0)
            lp.topMargin = clickPosition.get(1) - getBarHeight()
            //设置参数
            dragItem?.layoutParams = lp
            dragItem?.setImageBitmap(newBitmap)
            //我们在根布局容纳复制品的容器，显示显示，并把复制品添加到上面
            flMoveContent.visibility = View.VISIBLE
            flMoveContent.addView(dragItem)
        }
    }
</code></pre>
<p>这里我们处理复制，和把这个复制品添加到外部容器中，接下来手指拖动的时候整个rv会滚动起来，我们需要拦截一下，拦截条件就是：触发长按后，开始拦截rv的事件，不做滚动，手指移动的时候，我们接管这个事件，包括：down，move，up
</p>
<h3>处理item跟随手指移动</h3>
<p>实现接口RecyclerView.OnItemTouchListener</p>
<pre><code>处理是否消费事件，上面说了条件：长按触发消费，松开手指后，释放，继续让rv默认处理
onInterceptTouchEvent
处理具体的down，move，up，cancel
onTouchEvent
onRequestDisallowInterceptTouchEvent
</code></pre>
<p>适配相关配置如下</p>
<pre><code class="language-kotlin">    private fun initAdapter() {
        //new适配器
        dragAdapter = DragAdapter()
        //关闭rv的item动画
        RVUtils.disableAnim(rvTestDrag)
        //3列的方式展示
        val manager = GridLayoutManager(this, 3)
        rvTestDrag.layoutManager = manager
        rvTestDrag.adapter = dragAdapter
        //长按事件
        dragAdapter.setOnItemLongClickListener(this)
        //item的触摸事件
        rvTestDrag.addOnItemTouchListener(this)
        dragAdapter.setNewData(list)
    }
</code></pre>
<p>标记是否拦截rv的触摸</p>
<pre><code class="language-kotlin">    override fun onItemLongClick(adapter: BaseQuickAdapter&lt;*, *&gt;, view: View, position: Int): Boolean {
        //长按事件
        generateNewItem(view)
        //标记：当前rv不可滚动，事件拦截下来，我们自己处理
        rvCanMove = false
        //消费这个长按
        return true
    }
</code></pre>
<p>这里就是处理拦截和触摸了，流程：当长按之后，我们自己处理触摸，释放手指后，触摸归还rv。触摸期间，我们需要把手指移动的坐标同步到外部的复制品中松开了，就计算距离，通过简单线性动画移动到原位。</p>
<pre><code class="language-kotlin">    override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
        //触发长按之后,item的触摸事件来到这里了.MotionEvent返回手指移动的位置.以及up事件
        //得到当前手指位置
        val x2 = e.rawX
        val y2 = e.rawY
        when (e.action) {
            MotionEvent.ACTION_MOVE -&gt; {
                val moveX = x2 - lastX
                val moveY = y2 - lastY
                dragItem?.apply {
                    val tranX = translationX + moveX
                    val tranY = translationY + moveY
                    //移动这个复制出来的item
                    translationX = tranX
                    translationY = tranY
                }
            }
            MotionEvent.ACTION_UP -&gt; {
                //判断区域,手指的位置,是否在垃圾桶矩形内
                if (e.rawX &gt;= loc.get(0) &amp;&amp; e.rawX &lt;= endX
                        &amp;&amp; e.rawY &gt;= loc.get(1) &amp;&amp; e.rawY &lt;= endY
                ) {
                    //当手指松开之后，标记设置rv可以自由处理触摸
                    rvCanMove = true
                    //根容器移除复制品，同时隐藏起来
                    flMoveContent.removeAllViews()
                    flMoveContent.visibility = View.GONE
                    // TODO: 2020-09-04 进入矩形之后,做我们的操作,比如触发删除
                    Toast.makeText(this, &quot;已经收藏好了&quot;, Toast.LENGTH_SHORT).show()
                    //deleteItem()
                    dragAdapter.data.removeAt(currentIndex)
                    dragAdapter.notifyDataSetChanged()
                } else {
                    //恢复位置,如果item不是在垃圾桶位置,松开了.就需要返回原来的位置
                    val currentLocalPosition = IntArray(2)
                    dragItem?.apply {
                        //得到现在复制品位置
                        getLocationOnScreen(currentLocalPosition)
                        //拿到位置,飞回去
                        val ani = AniUtils.getTranslateAnimation(
                                //开始点击的位置,和现在up的位置计算出飞行的距离
                                clickPosition.get(0) - currentLocalPosition.get(0),
                                clickPosition.get(1) - currentLocalPosition.get(1),
                                //动画时间
                                1000,
                                Animation.ABSOLUTE,
                                false
                        )
                        doAnimation(this, ani)
                    }
                }
            }
        }
        //更新最新的位置
        lastX = x2
        lastY = y2
    }
    override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
        when (e.action) {
            MotionEvent.ACTION_DOWN -&gt; {
                //触发down的时候，把手指的位置记录好
                lastX = e.rawX
                lastY = e.rawY
            }
            MotionEvent.ACTION_MOVE -&gt; {
                // TODO: 2020-09-04 log,查看位置移动位置
            }
            MotionEvent.ACTION_UP -&gt; {
                //释放的时候，必须恢复可以滚动，移除伪装的view（复制品）
                resetItem()
            }
            MotionEvent.ACTION_CANCEL -&gt; {
                //释放的时候，必须恢复可以滚动，移除伪装的view（复制品）
                resetItem()
            }
        }
        //是否消费
        return !rvCanMove
    }
</code></pre>
<p>最后的长按代码</p>
<pre><code class="language-kotlin">    override fun onItemLongClick(adapter: BaseQuickAdapter&lt;*, *&gt;, view: View, position: Int): Boolean {
        //长按的时候,把原始的item设置隐藏,显示我们复制的view
        dragAdapter.data[position].isHide = true
        dragAdapter.notifyItemChanged(position)
        currentIndex = position
        //长按事件
        generateNewItem(view)
        rvCanMove = false
        //消费这个长按
        return true
    }
</code></pre>
<h3>我们的布局设置</h3>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;com.x.rv.drag.ItemDragActivity&quot;&gt;
    &lt;!--列表距离右边100dp--&gt;
    &lt;androidx.recyclerview.widget.RecyclerView
        android:id=&quot;@+id/rvTestDrag&quot;
        android:layout_width=&quot;match_parent&quot;
        android:background=&quot;#888888&quot;
        android:layout_height=&quot;match_parent&quot;
        android:layout_marginRight=&quot;100dp&quot; /&gt;
    &lt;!--矩形100*100，item到这个位置，就需要出发我们删除，屏幕最右边--&gt;
    &lt;TextView
        android:id=&quot;@+id/iv_collection&quot;
        android:layout_width=&quot;100dp&quot;
        android:layout_height=&quot;100dp&quot;
        android:text=&quot;收入囊中&quot;
        android:gravity=&quot;center&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;
    &lt;!--和根布局一样大的容器，放复制品用的，默认隐藏--&gt;
    &lt;FraqmeLayout
        android:id=&quot;@+id/flMoveContent&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:clickable=&quot;true&quot;
        android:focusable=&quot;true&quot;
        android:visibility=&quot;gone&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</code></pre>
<p>看适配器代码</p>
<pre><code class="language-kotlin">package com.x.rv.drag
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.x.R
import com.x.rv.adapter.DragItem
class DragAdapter() : BaseQuickAdapter&lt;DragItem, BaseViewHolder&gt;(R.layout.item_drag) {
    override fun convert(helper: BaseViewHolder, item: DragItem) {
        helper.setVisible(R.id.ivContent, !item.isHide)
        helper.setImageResource(R.id.ivContent, item.bg)
    }
}
</code></pre>
<p>布局</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;&gt;
    &lt;ImageView
        android:id=&quot;@+id/ivContent&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;100dp&quot;
        android:layout_margin=&quot;5dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        android:src=&quot;@drawable/ic_on&quot; /&gt;
&lt;/RelativeLayout&gt;
</code></pre>
<h3>总结</h3>
<p>功能主要是思路有了，再慢慢细分实现，当时写这个功能的时候ios的同事也在烦恼，做得差不多了后大家讨论实现居然神相似，哈哈。</p>
<blockquote>
    <p>功能用到知识 rv的基本数据显示 长按事件 复制view 获取view在屏幕中位置 触摸处理 拦截view事件 简单view动画</p>
</blockquote>
<p>最后看看完整的效果图</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a44c219aa614359877f6ba44df6c432~tplv-k3u1fbpfcp-watermark.image"
        alt="08.gif" /></p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acfeabfe5f8746e6b64df491e2ade8f2~tplv-k3u1fbpfcp-watermark.image"
        alt="07.gif" /></p>
<p>如果你有更骚的方式记得来分享~</p>
<p><img src="https://images.sunofbeaches.com/content/2021_07_04/861347524387012608.jpg" alt="图片描述" /></p>